VCST-5468: Resolve IMediator from request scope in GraphQL builders and add IRequestScopedCache#76
Open
alexeyshibanov wants to merge 9 commits into
Open
VCST-5468: Resolve IMediator from request scope in GraphQL builders and add IRequestScopedCache#76alexeyshibanov wants to merge 9 commits into
alexeyshibanov wants to merge 9 commits into
Conversation
…nd add IRequestScopedCache Graph types and schema builders are singletons; a ctor-injected IMediator is root-bound and cannot reach Scoped handler dependencies. GetResponseAsync and all resolver closures now resolve the mediator via context.GetMediator() (request scope, null-guarded InvalidOperationException). Old ctors are kept as [Obsolete] (VC0015) delegating overloads - nothing breaks at compile time. Adds IRequestScopedCache: Scoped GetOrAddAsync(key, factory) primitive deduplicating a load re-issued with identical arguments within one GraphQL request (first consumer: x-cart, follow-up). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…veFieldContextExtensions RequestScopedCache: store the Task<T> itself (no value-type boxing), static GetOrAdd lambda (no per-call closure), no async state machine, null-guard on factory. ResolveFieldContextExtensions: C# 14 extension member block; SetCurrencies no longer re-enumerates the source; tolerate IValueObject implementors not derived from ValueObject. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tScopedCache QueryArguments constructed explicitly (collection expression lowered through an empty array). RequestScopedCache.GetOrAddAsync uses the GetOrAdd(key, valueFactory, factoryArgument) overload with a static lambda - no closure allocation on the hit path. S3267 (foreach+if vs Where) is deliberately left: the loop form avoids the Where iterator allocation and keeps the skip reason at the use site; the quality gate is not affected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…opedCache docs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
OlegoO
approved these changes
Jul 14, 2026
alexeyshibanov
force-pushed
the
feat/VCST-5468
branch
from
July 14, 2026 16:25
0fc6999 to
903fc58
Compare
Per-id promise reservation (TCS) bounds concurrent loads to the distinct union of missing ids; tuple-keyed entries can't alias by-key entries; null load result counts as empty (platform GetOrLoadByIdsAsync semantics). IEntity sugar as an extension. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Method-per-phase split (guards / classify / reserve / load+publish / fault / collect); behavior and allocations unchanged, unified try/catch over classification+dispatch kept. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rename from GetOrAddAsync overload; ids as ICollection<string> (no lazy re-enumeration in overrides); result as IDictionary<string, T> - created per call, callers may mutate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ICollection input keeps zero-copy Keys passing; IList result matches VC service returns (Task<T> invariance otherwise forces an async wrapper at every call site) and bans deferred re-enumeration in overrides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Problem
graphql-dotnet
*Typeclasses andISchemaBuilderimplementations are singletons (the schema is built once). A ctor-injectedIMediatoris captured against the root service provider, so every handler it dispatches to resolves from the root scope:AddScopeddependency, requests fail withCannot resolve scoped service ... from root provider— surfacing as HTTP 200 with per-field errors (invisible to status-only smoke checks). WithValidateScopesoff, the scoped service is silently promoted to an app-global singleton (cross-request state sharing).Changes
RequestBuilder.GetResponseAsyncresolves the mediator via the newIResolveFieldContext.GetMediator()extension — per-request scope, null-guarded with a diagnosticInvalidOperationExceptionwhenRequestServicesis not populated.IMediatoracross the builder chain (RequestBuilder,CommandBuilder,QueryBuilder,SearchQueryBuilder,LocalizedSettingQueryBuilder,GetStoreQueryBuilder,SlugInfoQueryBuilder) and the graph types / schema builders that captured a mediator (CountryType,DynamicPropertyType,DynamicPropertyValueType,CoreSchema,DynamicPropertySchema). Old constructors are kept as[Obsolete]delegating overloads (DiagnosticId = "VC0015") — binary- and source-compatible; consumers migrate at their own pace (or add<NoWarn>VC0015</NoWarn>underTreatWarningsAsErrors).IRequestScopedCache(Scoped):GetOrAddAsync<T>(key, factory)— request-scoped memoization that dedups a load re-issued with identical arguments many times within one GraphQL request. Complements DataLoader: DataLoader dedups by node key, this cache dedups by the load's own argument key. Implementation stores theTask<T>itself (no value-type boxing, no per-call closure), single-flight viaLazy.IGraphType/ISchemaBuilderimplementation may takeIMediator.ResolveFieldContextExtensionsconverted to C# 14 extension members;SetCurrenciesno longer re-enumerates its source.Notes
context.RequestServices.RequestServicescarries a live DI scope on all HTTP paths (single, batched sequential/parallel, GraphiQL). Subscription per-event resolution under the default strategy does not (the subscribe-time scope is disposed right after subscribe) — no resolver touched by this change is reachable there; if an emitting subscription ever needs scoped services, registerScopedSubscriptionExecutionStrategy.IRequestScopedCachelands in a follow-up (x-cart configuration-query dedup, VCST-5303): measured on a real storefront cart read path — ES searches per read 207 -> 5, k6 50-VU p95 6042 -> 152 ms.🤖 Generated with Claude Code
Note
Medium Risk
Changes the core GraphQL-to-Mediator path and DI lifetimes across many types; behavior is intended to fix scoped-service bugs but any missed resolver or test without RequestServices could fail at runtime.
Overview
Fixes singleton GraphQL schema/types capturing
IMediatorfrom the root scope, which breaks or mis-scopes handler dependencies. Dispatch now usesIResolveFieldContext.GetMediator()(fromRequestServicesinside resolvers);RequestBuilder.GetResponseAsyncand affected graph types/schema builders follow that pattern instead of a stored mediator.Constructors on query/command builders and core graph types gain
IAuthorizationService-only entry points;IMediatorctors remain as[Obsolete](VC0015) delegating overloads for binary/source compatibility.Adds
IRequestScopedCache(scoped DI) withGetOrAddAsyncand batchGetOrLoadByIdsAsync, plus anIEntityid extension—intended for per-request dedup of repeated loads (follow-up consumers).ResolveFieldContextExtensionsis refactored to C# 14 extension members (includingGetMediator).Tests cover request-scoped mediator resolution, a no non-obsolete
IMediatorctor guard onIGraphType/ISchemaBuilder, and broadRequestScopedCachebehavior.Reviewed by Cursor Bugbot for commit 6446604. Bugbot is set up for automated code reviews on this repo. Configure here.
Jira-link:
https://virtocommerce.atlassian.net/browse/VCST-5468
Artifact URL:
https://vc3prerelease.blob.core.windows.net/packages/VirtoCommerce.Xapi_3.1014.0-alpha.194-vcst-5468.zip